#include "search.h"
#include "../base/client-info.h"
#include "../base/log.h"

#include <vector>
#include <string>

using std::string;
using std::set;
using std::vector;

void split(const string& text, vector<string>* words) {
    words->clear();

    string temp;
    for (int i = 0; i < (int)text.size(); ++i) {
        if (isalpha(text[i])) {
            temp.push_back(tolower(text[i]));
        } else {
            if (!temp.empty())
                words->push_back(temp);
            temp.clear();
        }
    }

    if (!temp.empty())
        words->push_back(temp);
}

uint64_t word_to_int64(const string& word) {
    if (word.size() > 13) {
        LOG(WARN) << "Lungimea cuvantului este mai mare decat 13. "
                  << "Truncam la primele 13 litere.";
    }

    uint64_t value = 1;
    for (int i = 0; i < 13 && i < (int)word.size(); ++i) {
        ASSERT(islower(word[i]));
        value *= 26;
        value += word[i]-'a';
    }

    return value;
}

uint32_t hash_function(uint64_t word, int number) {
    ASSERT(number == 0 || number == 1);
    CHECK_NE(word, 0);

    static const uint64_t scale[2] = {
        883179356724199ULL,
        9740433453590341ULL
    };
    uint64_t a = scale[number];
    uint64_t b = scale[number];
    uint64_t c = word;;

    a = a-b; a = a-c; a = a^(c>>43);
    b = b-c; b = b-a; b = b^(a<<9);
    c = c-a; c = c-b; c = c^(b>>8);
    a = a-b; a = a-c; a = a^(c>>38);
    b = b-c; b = b-a; b = b^(a<<23);
    c = c-a; c = c-b; c = c^(b>>5);
    a = a-b; a = a-c; a = a^(c>>35);
    b = b-c; b = b-a; b = b^(a<<49);
    c = c-a; c = c-b; c = c^(b>>11);
    a = a-b; a = a-c; a = a^(c>>12);
    b = b-c; b = b-a; b = b^(a<<18);
    c = c-a; c = c-b; c = c^(b>>22);

    return (uint32_t)(c >> 8); 
}

const FingerPrint* Search::fingerprint(const FingerPrint* finger) {
    typeof(fingers_.begin()) it = fingers_.find(finger);

    if (it == fingers_.end())
        it = fingers_.insert(finger->clone()).first;

    return *it;
}

bool Search::add_client(const ClientInfo* client) {
    const vector<FileList::File*> list = client->filelist()->list();

    for (int i = 0; i < (int)list.size(); ++i) {
        vector<string> words;
        split(list[i]->name(), &words);

        // construim o copie locala
        const FingerPrint* finger = fingerprint(&list[i]->fingerprint());

        // introducem numele de fisier in lista numelor
        // pentru un anumit
        filelist_[finger].push_back(make_pair(client->id(), list[i]->name()));

        for (int w = 0; w < (int)words.size(); ++w) {
            uint64_t value = word_to_int64(words[i]);

            // inseram in hash fisierele
            insert_print(value, finger);
        }
    }

    adjust_hashtable();
    return false;
}

void Search::insert_print(uint64_t word, const FingerPrint* finger) {
    uint32_t hash[2] = {
        hash_function(word, 0) % hashsize_,
        hash_function(word, 1) % hashsize_
    };

    // determinam in care din cele doua hashuri inseram cuvantul
    typeof(hash_[0][hash[0]])* where = &hash_[0][hash[0]];
    if (hash_[hash[1]].size() < hash_[hash[0]].size())
        where = &hash_[1][hash[1]];

    // daca nu este retinut deja cuvantul, il introducem in map
    typeof(where->begin()) it = where->find(word);
    if (it == where->end())
        it = where->insert(
            make_pair(word,
                      set<const FingerPrint*, CompareFingerPrintPointer>())).first;

    it->second.insert(finger);
}

void Search::del_client(ClientId client) {
    // TODO: implement
}
